﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using Thor.Common;
using Thor.Configuration;
using Thor.Domain;
using Thor.Domain.Configuration;

namespace Thor.Domain
{
    public class CalendarProviderHelper
    {
        #region fields

        private static ConfigurationSettings _ConfigSettings = new ConfigurationSettings();
        private static string _ConnectionStringKey = ConfigurationSettings.ConnectionStringKey;
        private static string _ConnectionStringValue = ConfigurationSettings.ConnectionStringValue;
        private static string _ContainerAddressKey = ConfigurationSettings.ContainerAddressKey;
        private static string _ContainerAddressValue = ConfigurationSettings.ContainerAddressValue;
        private static string _RepositoryAssembly = AppDomain.CurrentDomain.BaseDirectory + ConfigurationSettings.AssemblyRepositoryAssembly;
        private static string _RepositoryType = ConfigurationSettings.AssemblyRepositoryType;
        private static string _CalendarProviderBaseClassName = ConfigurationSettings.CalendarProviderInterfaceName;

        #endregion

        #region constructors

        private CalendarProviderHelper() { }

        #endregion

        #region properties

        private static ConfigurationSettings ConfigurationSettings
        {
            get
            {
                if (_ConfigSettings == null)
                    _ConfigSettings = new ConfigurationSettings();

                return _ConfigSettings;
            }
        }

        public static bool IsCalendarProviderConfigured()
        {
            var settings = SettingsHelper.GetSettings();
            var assembly = GetManager().GetAssembly(settings.AssemblyName);

            if (assembly == null)
                return false;
            else
                return true;
        }

        #endregion

        #region methods

        public static AppointmentResponse BookAppointment(Guid calendarId, DateTime startTime, DateTime endTime, string subject)
        {
            var calendar = CalendarHelper.GetCalendar(calendarId);

            TimeZoneManager timeZoneManager = new TimeZoneManager();
            TimeZoneInfo timeZoneInfo = timeZoneManager.GetTimeZoneInfoById(calendar.TimeZone);

            var settings = SettingsHelper.GetSettings();

            var appointmentRequest = new AppointmentRequest();

            NetworkCredential credential = SettingsHelper.GetNetworkCredential();

            appointmentRequest.Credential = credential;
            appointmentRequest.BookingAgentEmail = settings.BookingAgentEmail;
            appointmentRequest.IgnoreBusyData = true;
            appointmentRequest.ServiceUrl = settings.CalendarProviderUri;

            if (settings.UseBookingAgent)
                appointmentRequest.UseBookingAgent = settings.UseBookingAgent;
            else
                appointmentRequest.UseBookingAgent = calendar.UseBookingAgent;

            appointmentRequest.CalendarId = calendar.CalendarEmail;
            appointmentRequest.Subject = subject;

            DateTime localAppointmentStartTime = DateTime.SpecifyKind(startTime, DateTimeKind.Unspecified);
            DateTime localAppointmentEndTime = DateTime.SpecifyKind(endTime, DateTimeKind.Unspecified);

            if (appointmentRequest.UseBookingAgent)
            {
                appointmentRequest.StartTime = localAppointmentStartTime;
                appointmentRequest.EndTime = localAppointmentEndTime;
            }
            else
            {
                DateTime utcAppointmentStartTime = TimeZoneInfo.ConvertTimeToUtc(localAppointmentStartTime, timeZoneInfo);
                DateTime utcAppointmentEndTime = TimeZoneInfo.ConvertTimeToUtc(localAppointmentEndTime, timeZoneInfo);

                appointmentRequest.StartTime = utcAppointmentStartTime;
                appointmentRequest.EndTime = utcAppointmentEndTime;
            }

            var provider = CalendarProviderHelper.GetCalendarProvider(settings.AssemblyName, settings.AssemblyType);
            var response = provider.BookAppointment(appointmentRequest);

            if (response.Success)
            {
                DateTime dateTimeUtc = DateTime.UtcNow;
                DateTime dateTimeLocal = timeZoneManager.ConvertTimeFromUtc(dateTimeUtc, calendar.TimeZone);
                
                DateTime startDateTime = new DateTime(dateTimeLocal.Year, dateTimeLocal.Month, dateTimeLocal.Day);
                DateTime endDateTime = new DateTime(dateTimeLocal.Year, dateTimeLocal.Month, dateTimeLocal.Day).AddHours(23).AddMinutes(59);

                DateTime localStartDateTime = DateTime.SpecifyKind(startDateTime, DateTimeKind.Unspecified);
                DateTime localEndDateTime = DateTime.SpecifyKind(endDateTime, DateTimeKind.Unspecified);

                CalendarRequest calendarRequest = new CalendarRequest();

                calendarRequest.CalendarId = calendar.CalendarEmail;
                calendarRequest.Alias = calendar.Alias;
                calendarRequest.Credential = credential;
                calendarRequest.ServiceUrl = settings.CalendarProviderUri;

                calendarRequest.StartDate = TimeZoneInfo.ConvertTimeToUtc(localStartDateTime, timeZoneInfo);
                calendarRequest.EndDate = TimeZoneInfo.ConvertTimeToUtc(localEndDateTime, timeZoneInfo);

                var updatedCalendar = provider.GetCalendar(calendarRequest);

                updatedCalendar.Id = calendar.Id;
                updatedCalendar.TimeZone = calendar.TimeZone;
                updatedCalendar.PublicCanBook = calendar.PublicCanBook;
                updatedCalendar.PublicCanView = calendar.PublicCanView;
                updatedCalendar.UseBookingAgent = calendar.UseBookingAgent;
                updatedCalendar.DisplaySubject = calendar.DisplaySubject;
                updatedCalendar.UseTimeframe = calendar.UseTimeframe;
                updatedCalendar.StartTime = calendar.StartTime;
                updatedCalendar.EndTime = calendar.EndTime;

                updatedCalendar.ConvertFromUtc();

                CalendarHelper.SaveCalendar(updatedCalendar);
            }

            return response;
        }

        public static CalendarProviderBase GetCalendarProvider(string assemblyName, string typeName)
        {
            var assembly = GetManager().GetAssembly(assemblyName);

            if (assembly == null)
                throw new ThorNotConfiguredException("Thor's calendar provider has not been configured.");

            var type = assembly.GetType(typeName);

            Type[] constructorTypes = new Type[0];
            ConstructorInfo constructorInfo = type.GetConstructor(constructorTypes);

            if (constructorInfo != null)
                return constructorInfo.Invoke(null) as CalendarProviderBase;

            return null;
        }

        private static Dictionary<string, object> GetRepositoryParameters()
        {
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add(_ConnectionStringKey, _ConnectionStringValue);
            parameters.Add(_ContainerAddressKey, _ContainerAddressValue);
            return parameters;
        }

        private static IProviderRepository GetRepository(Dictionary<string, object> parameters)
        {
            return ProviderManager.GetRepository(_RepositoryAssembly, _RepositoryType, parameters);
        }

        public static ProviderManager GetManager()
        {
            Dictionary<string, object> parameters = GetRepositoryParameters();
            IProviderRepository repository = GetRepository(parameters);
            ProviderManager manager = new ProviderManager(repository, _CalendarProviderBaseClassName);
            return manager;
        }

        #endregion
    }
}
